﻿Imports System.Text

Public Class AutoTrackingNumber
    Implements IShipmentTrackingNumber

    Private _ShipmentCompany As ShippingCompany
    Private _TrackingNumber As String
    Private _TrackingNumberReversed As String

#Region "Public Methods"
    Public Sub New(ByVal trackingNumber As String)
        Me._TrackingNumber = trackingNumber

        Dim chars As Char() = trackingNumber.ToCharArray()
        Array.Reverse(chars)
        Me._TrackingNumberReversed = New String(chars)

        DetermineShippingCompany()
    End Sub
#End Region

#Region "Private Methods"
    Private Sub DetermineShippingCompany()
        If IsFedExExpressShipment() Then
            Me._ShipmentCompany = ShippingCompany.FedExExpress
        ElseIf IsFedExGroundShipment() Then
            Me._ShipmentCompany = ShippingCompany.FedExGround
        ElseIf IsUpsShipment() Then
            Me._ShipmentCompany = ShippingCompany.UPS
        ElseIf IsUspsShipment_20Digit() OrElse IsUspsShipment_22Digit() Then
            Me._ShipmentCompany = ShippingCompany.USPS
        Else
            Me._ShipmentCompany = ShippingCompany.Unknown
        End If
    End Sub

    Private Function IsFedExExpressShipment() As Boolean
        Dim isFedExExpress As Boolean = False

        If _TrackingNumber.Length = 12 Then
            Dim digit01 As Integer = Val(_TrackingNumber(0)) * 3
            Dim digit02 As Integer = Val(_TrackingNumber(1)) * 1
            Dim digit03 As Integer = Val(_TrackingNumber(2)) * 7
            Dim digit04 As Integer = Val(_TrackingNumber(3)) * 3
            Dim digit05 As Integer = Val(_TrackingNumber(4)) * 1
            Dim digit06 As Integer = Val(_TrackingNumber(5)) * 7
            Dim digit07 As Integer = Val(_TrackingNumber(6)) * 3
            Dim digit08 As Integer = Val(_TrackingNumber(7)) * 1
            Dim digit09 As Integer = Val(_TrackingNumber(8)) * 7
            Dim digit10 As Integer = Val(_TrackingNumber(9)) * 3
            Dim digit11 As Integer = Val(_TrackingNumber(10)) * 1

            Dim sum As Integer = digit01 + digit02 + digit03 + digit04 + digit05 + digit06 + digit07 + digit08 + digit09 + digit10 + digit11

            Dim remainder As Integer = sum Mod 11
            If remainder = 10 Then
                remainder = 0
            End If

            isFedExExpress = (Val(_TrackingNumber(11)) = remainder)
        End If

        Return isFedExExpress
    End Function

    Private Function IsFedExGroundShipment() As Boolean
        Dim isFedExGround As Boolean = False
        Dim checkDigit As Integer = -1

        If _TrackingNumber.Length = 15 Then
            Dim evensSum As Integer = (Val(_TrackingNumberReversed(1)) +
                                       Val(_TrackingNumberReversed(3)) +
                                       Val(_TrackingNumberReversed(5)) +
                                       Val(_TrackingNumberReversed(7)) +
                                       Val(_TrackingNumberReversed(9)) +
                                       Val(_TrackingNumberReversed(11)) +
                                       Val(_TrackingNumberReversed(13))) * 3

            Dim oddsSum As Integer = (Val(_TrackingNumberReversed(2)) +
                                      Val(_TrackingNumberReversed(4)) +
                                      Val(_TrackingNumberReversed(6)) +
                                      Val(_TrackingNumberReversed(8)) +
                                      Val(_TrackingNumberReversed(10)) +
                                      Val(_TrackingNumberReversed(12)) +
                                      Val(_TrackingNumberReversed(14)))

            Dim totalSum As Integer = evensSum + oddsSum
            checkDigit = 10 - (totalSum Mod 10)
        End If

        isFedExGround = (Val(_TrackingNumberReversed(0)) = checkDigit)

        Return isFedExGround
    End Function

    Private Function IsUpsShipment() As Boolean
        Dim isUps As Boolean = False

        If _TrackingNumber.Substring(0, 2) = "1Z" Then
            Dim trimedTrackingNumber As String = _TrackingNumber.Substring(2).ToUpper()
            Dim substitutedTrackingNumber As New StringBuilder

            For i As Integer = 0 To trimedTrackingNumber.Length - 1
                If ((trimedTrackingNumber(i) >= "A"c) AndAlso
                    (trimedTrackingNumber(i) <= "Z"c)) Then
                    substitutedTrackingNumber.Append((AscW(trimedTrackingNumber(i)) - 3) Mod 10)
                Else
                    substitutedTrackingNumber.Append(Val(trimedTrackingNumber(i)))
                End If
            Next

            isUps = VerifyMod10CheckDigit(substitutedTrackingNumber.ToString(), 2)
        End If

        Return isUps
    End Function

    Private Function IsUspsShipment_20Digit() As Boolean
        Dim isUsps As Boolean = False

        If _TrackingNumber.Length = 20
            isUsps = VerifyMod10CheckDigit("91" + _TrackingNumber, 3, True)
        End If

        Return isUsps
    End Function

    Private Function IsUspsShipment_22Digit() As Boolean
        Dim isUsps As Boolean = False

        If _TrackingNumber.Length = 22
            isUsps = VerifyMod10CheckDigit(_TrackingNumber, 3, True)
        End If

        Return isUsps
    End Function

    Private Function VerifyMod10CheckDigit(ByVal value As String, ByVal evensMultiple As Integer, Optional ByVal rightToLeft As Boolean = False)
        Dim isValid As Boolean = False
        Dim evensSum As Integer = 0
        Dim oddsSum As Integer = 0
        Dim checkDigit As Integer = -1

        For i As Integer = 0 To value.Length - 2
            If i Mod 2 = 0 Then
                If rightToLeft
                    evensSum += (Val(value(i)) * evensMultiple)
                Else
                    oddsSum += Val(value(i))
                End If
            Else
                If rightToLeft
                    oddsSum += Val(value(i))
                Else
                    evensSum += (Val(value(i)) * evensMultiple)
                End If
            End If
        Next

        Dim totalSum As Integer = evensSum + oddsSum
        checkDigit = 10 - (totalSum Mod 10)

        isValid = (Val(value(value.Length - 1)) = checkDigit)

        Return isValid
    End Function
#End Region

#Region "Properties"
    Public ReadOnly Property ShipmentCompany As ShippingCompany Implements IShipmentTrackingNumber.ShipmentCompany
        Get
            Return Me._ShipmentCompany
        End Get
    End Property

    Public ReadOnly Property TrackingNumber As String Implements IShipmentTrackingNumber.TrackingNumber
        Get
            Return Me._TrackingNumber
        End Get
    End Property
#End Region
End Class